
Alkaline.Modules.newModule('Printer', function(){

this.moduleType = Alkaline.MODULE_TYPES.OUTPUT;

this.registerSupportedArgument('output', 'o', 1, null);
this.registerSupportedArgument('prettyPrint', 'pp', 0, false);

var TOKEN = Alkaline.JSInfo.TOKEN;
var tokenNames = Alkaline.JSInfo.tokenNames;
var exampleParser = new AlkalineParser();
this.precedence = exampleParser.precedence;
this.opIsAssignment = exampleParser.opIsAssignment;
this.getOpPriority = exampleParser.getOpPriority;
exampleParser = null;	// free the memory

this.keywords = ['break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete', 'do', 'else', 'false', 'finally', 'for', 'function', 'if', 'in',
	'instanceof', 'new', 'null', 'return', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with'];
this.futureKeywords = ['class', 'const', 'enum', 'export', 'extends', 'import', 'super'];
this.futureStrictKeywords = ['implements', 'interface', 'let', 'package', 'private', 'protected', 'public', 'static', 'yield'];

var kwNonStrict = this.keywords.concat(this.futureKeywords);
var kwStrict = kwNonStrict.concat(this.futureStrictKeywords);

this.strictMode = false;
this.e4xGlobalSelect = /^('|")@?\*\1$/;

this.testStrict = function(ast) {
	if (ast.children && ast.children.length && ast.children[0].token.type === TOKEN.String &&
		ast.children[0].token.text.substr(1, ast.children[0].token.text.length-2) == "use strict") {
		this.kwNow = kwStrict;
		this.kwNowStr = ',' + this.kwNow.join(',') + ',';
		return this.strictMode = true;
	}
}

this.exitStrictMode = function() {
	this.kwNow = kwNonStrict;
	this.strictMode = false;
	this.kwNowStr = ',' + this.kwNow.join(',') + ',';
}

/**
 *	run ( ast )
 */
this.run = function(ast) {
	this._setupPrinter(ast);
	this.kwNow = kwNonStrict;
	this.strictMode = false;
	this.kwNowStr = ',' + this.kwNow.join(',') + ',';
	this.testStrict(ast);
	this.DFS(ast, [ TOKEN.If ], this.markRequiredFakeBlocks);
	this.outputText = this.stringify(ast);
	var outFile = this.arguments.output;
	if (outFile)
		writeFile(outFile, this.outputText);
	else
		this.compileResult._msg("No output file specified");
}

this._setupPrinter = function(ast) {
	this.indentation = 0;
	this.indentStr = '';
	this.indentLine = this.arguments.prettyPrint ? '\n' : '';
	this.space = this.arguments.prettyPrint ? ' ' : '';
	this.hasE4X = false;
	this.DFS(ast, [ TOKEN.XmlElement, TOKEN.XmlNodeList, TOKEN.XmlCData, TOKEN.FILTER ], this.markHasE4X);
	if (this.hasE4X)
		this.idRx = /^('|")@?[a-zA-Z_][a-zA-Z_0-9]*\1$/;
	else
		this.idRx = /^('|")[a-zA-Z_][a-zA-Z_0-9]*\1$/;
}

this.markHasE4X = function() {
	this.hasE4X = true;
	return this.stopSearch;
}

this.markRequiredFakeBlocks = function(ast) {
	if (ast.children.length > 2)
		// we do this only for if-then (no else)
		return;
	var maybeHash = Alkaline.JSInfo.endsInExpression.maybeHash;
	while (ast.parent) {
		var idx = ast.childIndex;
		ast = ast.parent;
		if (ast.token.type === TOKEN.If && ast.children.length === 3 && idx === 1) {
			ast.children[1].useFakeBlock = true;
			return;	// mission accomplished
		}
		if (!maybeHash[ast.token.type] || idx !== ast.children.length-1)
			// the chain has been broken
			return;
	}
}

this.incIndentation = function() {
	if (!this.arguments.prettyPrint) return;
	this.indentation++;
	this.indentStr = new Array(this.indentation + 1).join('\t');
	this.indentLine = '\n' + this.indentStr;
}

this.decIndentation = function() {
	if (!this.arguments.prettyPrint) return;
	this.indentation--;
	this.indentStr = new Array(this.indentation + 1).join('\t');
	this.indentLine = '\n' + this.indentStr;
}


this._useParanForCALLorSELECT = function(ast) {
	var child = ast.children[0].token.type;
	if (this._useParanForCALLorSELECT.tokens[child])
		return true;
	return false;
}
this._useParanForCALLorSELECT.tokens = {};
this._useParanForCALLorSELECT.tokens[TOKEN.Operator] = true;
this._useParanForCALLorSELECT.tokens[TOKEN.UnaryOperator] = true;
this._useParanForCALLorSELECT.tokens[TOKEN.PostOp] = true;

this._useParanToStringifyFunction = function(ast) {
	if (ast.isFunctionDeclaration)
		return false;
	while (ast.parent) {
		var lastChildIdx = ast.childIndex;
		ast = ast.parent;
		switch (ast.token.type) {
			case TOKEN.Id:
			case TOKEN.Return:
			case TOKEN.Switch:
			case TOKEN.UnaryOperator:
			case TOKEN.Throw:
				return false;
			
			case TOKEN.BLOCK:
				return true;
			
			case TOKEN.If:
				return lastChildIdx;	// 0 -> false, 1 -> true
			
			case TOKEN.For:
			case TOKEN.While:
			case TOKEN.With:
				return lastChildIdx === ast.children.length - 1;
			
			case TOKEN.CALL:
			case TOKEN.EXPR:
			case TOKEN.Operator:
			case TOKEN.SELECT:
				if (lastChildIdx)
					return false;
		}
	}
	return true;	// should never be reached, but err on the safe side
}
//this._useParanToStringifyFunction._onlyIfFirst = [ TOKEN.CALL, TOKEN.EXPR, TOKEN.Operator, TOKEN.SELECT ];

// this is copied and slightly modified from Alkaline.JSInfo.endsInExpression
this.statementRequiresSemicolon = function(tree) {
	var maybeHash = Alkaline.JSInfo.endsInExpression.maybeHash;
	var type = tree.token.type;
	while (maybeHash[type]) {
		tree = tree.children[tree.children.length-1];
		type = tree.token.type;
	}
	if (type === TOKEN.Function)
		return !tree.isFunctionDeclaration;
	if (type === TOKEN.Do)
		return this.arguments.prettyPrint;
	return !!this.statementRequiresSemicolon.hash[type];
}
this.statementRequiresSemicolon.hash = Alkaline.JSInfo.endsInExpression.hash;
this.statementRequiresSemicolon.hash[TOKEN.Break] = true;
this.statementRequiresSemicolon.hash[TOKEN.Continue] = true;

this.stringify = function(ast) {
	return this['stringify' + ast.token.type](ast);
}

this.stringifyArgDecl = function(ast) {
	var list = [];
	if (ast.children)
		for (var i = 0; i < ast.children.length; i++)
			list.push(this.stringify(ast.children[i]));
	var separator = ',' + this.space;
	return '(' + list.join(separator) + ')';
}

this.stringifyARRAY = function(ast) {
	var separator = ',' + this.space;
	var str = '[' + this.space;
	if (ast.children) {
		for (var i = 0; i < ast.children.length; i++) {
			if (i) str += separator;
			if (ast.children[i].token.type != TOKEN.UNDEFINED)
				str += this.stringify(ast.children[i]);
		}
		if (ast.children.length)
			if (ast.children.last().token.type == TOKEN.UNDEFINED)
				str += separator;
			else
				str += this.space;
	}
	str += ']';
	return str;
}

this.stringifyArrayComprehension = function(ast) {
	var str = '[' + this.space + this.stringify(ast.children[0]) + ' ' + this.stringify(ast.children[1]) + this.space + ']';
	return str;
}

this.stringifyBLOCK = function(ast) {
	var childCount = ast.children && ast.children.length || 0;
	var innerBlock = ast.parent && ast.parent.token.type != TOKEN.Case && ast.parent.token.type != TOKEN.Default;
	if (!childCount)
		return innerBlock ? '{}' : '';
	var curly = this.stringifyBLOCKUsesCurly(ast);
	var str = '';
	if (curly) {
		str += '{';
		this.incIndentation();
		str += this.indentLine;
	}
	var indentStr, lastSeparator;
	indentStr = this.indentLine;
	//lastSeparator = (this.arguments.prettyPrint && (curly || !ast.parent)) || (!curly && ast.parent && ast.parent.token.type == TOKEN.If && ast.parent.children.length > ast.childIndex+1)
	//	? ';' : '';
	for (var i = 0; i < childCount; i++) {
		str += this.stringify(ast.children[i]);
		if (str.charAt(str.length-1) != ';') {
			var childType = ast.children[i].token.type;
			if (childType !== TOKEN.BLOCK && childType !== TOKEN.Comment) {
				if (i < childCount-1) {
					if (this.statementRequiresSemicolon(ast.children[i]))
						str += ';';
				} else
					if (this.arguments.prettyPrint && this.statementRequiresSemicolon(ast.children[i]))
						str += ';';
				//	str += lastSeparator;
			}
		}
		if (i < childCount-1)
			str += indentStr;
	}
	if (curly) {
		this.decIndentation();
		str += this.indentLine;
		str += '}';
	}
	return str;
}

this.stringifyBLOCKUsesCurly = function(ast) {
	if (!ast.parent)
		return false;
	if (ast.parent.token.type == TOKEN.Case || ast.parent.token.type == TOKEN.Default)
		return false;
	if (this.stringifyBLOCKUsesCurly.forceInside[ast.parent.token.type])
		return true;
	if (!ast.children || ast.children.length != 1)
		return true;
	var lastChild = ast.children && ast.children[ast.children.length-1];
	return false;
}
this.stringifyBLOCKUsesCurly.forceInside = {};
this.stringifyBLOCKUsesCurly.forceInside[TOKEN.Get] =
this.stringifyBLOCKUsesCurly.forceInside[TOKEN.Set] =
this.stringifyBLOCKUsesCurly.forceInside[TOKEN.BLOCK] =
this.stringifyBLOCKUsesCurly.forceInside[TOKEN.Function] =
this.stringifyBLOCKUsesCurly.forceInside[TOKEN.Try] =
this.stringifyBLOCKUsesCurly.forceInside[TOKEN.Catch] =
this.stringifyBLOCKUsesCurly.forceInside[TOKEN.Finally] =
this.stringifyBLOCKUsesCurly.forceInside[TOKEN.If] =
	true;

this._stringifyComposed = function(ast) {
	var top = ast;
	while (top && top.token.type != TOKEN.ArrayComprehension)
		top = top.parent;
	var arrCompr = !!top;
	if (ast.token.type === TOKEN.BLOCK && (!ast.children || !ast.children.length))
		return { str: !arrCompr ? this.arguments.prettyPrint ? this.space + '{}' : ';' : '', indent: false };
	
	//if (ast.parent && ast.token.type !== TOKEN.BLOCK && ast.parent.token.type === TOKEN.If && ast.parent.children.length === 3 && ast.childIndex === 1 && Alkaline.JSInfo.endsInIfThen(ast))
	if (ast.useFakeBlock)
		// fake a block because we need the curly braces
		ast = {
			children: [ast],
			parent: ast.parent,
			childIndex: 1,
			token: {
				type: TOKEN.BLOCK
			},
			fakeBlock: true
		};

	var indent = false;
	var str = '';
	if (this.arguments.prettyPrint) {
		if (ast.token.type === TOKEN.BLOCK && this.stringifyBLOCKUsesCurly(ast))
			str += this.space;
		else if (ast.token.type !== TOKEN.If || ast.parent.token.type !== TOKEN.If || ast.childIndex < 2) {
			if (arrCompr)
				str += this.space;
			else {
				this.incIndentation();
				str += this.indentLine;
				indent = true;
			}
		}
	} else if (ast.parent.token.type === TOKEN.Do && ast.token.type !== TOKEN.BLOCK)
		str += ' ';
	str += this.stringify(ast);
	if (indent)
		this.decIndentation();
	// inside block of an if-then-else or do-while
	if (ast.childIndex < ast.parent.children.length-1)
		if (this.statementRequiresSemicolon(ast))
			str += ';';
	return { str: str, indent: indent };
}

this.stringifyBreak =
this.stringifyContinue = function(ast) {
	var str = ast.token.text;
	if (ast.children && ast.children.length)
		str += ' ' + ast.children[0].token.text;
	return str;
}

this.stringifyCALL = function(ast) {
	var str = this.stringify(ast.children[0]);
	if (this._useParanForCALLorSELECT(ast))
		str = '(' + str + ')';
	str += '(';
	for (var i = 1; i < ast.children.length; i++) {
		if (i > 1)
			str += ',' + this.space;
		str += this.stringify(ast.children[i]);
	}
	str += ')';
	return str;
}

this.stringifyCase = function(ast) {
	var str = 'case ' + this.stringify(ast.children[0]) + ':';
	if (ast.children.length > 1) {
		this.incIndentation();
		var block = this.stringify(ast.children[1]);
		if (block) {
			str += this.indentLine + block;
			if (!this.arguments.prettyPrint && ast.childIndex < ast.parent.children.length-1)
				str += ';';
		}
		this.decIndentation();
	}
	return str;
}

this.stringifyCatch = function(ast) {
	var str = 'catch' + this.space + '(' + this.stringify(ast.children[0]);
	if (ast.children.length > 2)
		str += ' if ' + this.stringify(ast.children[1]);
	str += ')' + this.space + this.stringify(ast.children[ast.children.length-1]);
	return str;
}

this.stringifyComment = function(ast) {
	var str = ast.token.text;
	if (!this.arguments.prettyPrint && str.charAt(1) === '/' && (ast.parent.parent || ast.parent.children.length > ast.childIndex+1))
		// there are more instructions after this, and we are not pretty printing
		str += '\n';
	return str;
}

this.stringifyDefault = function(ast) {
	var str = 'default:';
	if (ast.children && ast.children.length) {
		this.incIndentation();
		var block = this.stringify(ast.children[0]);
		if (block) {
			str += this.indentLine + block;
			if (!this.arguments.prettyPrint && ast.childIndex < ast.parent.children.length-1)
				str += ';';
		}
		this.decIndentation();
	}
	return str;
}

this.stringifyDefaultDirective = function(ast) {
	var str = 'default ' + ast.children[0].token.text + ' ' + ast.children[1].token.text + this.space + '=' + this.space + this.stringify(ast.children[2]);
	return str;
}

this.stringifyDo = function(ast) {
	var str = 'do';
	var block = this._stringifyComposed(ast.children[0]);
	str += block.str;
	if (block.indent) {
		//str += ';';
		str += this.indentLine;
	} else
		str += this.space;
	str += 'while' + this.space + '(' + this.stringify(ast.children[1]) + ')';
	return str;
}

this.stringifyEXPR = function(ast) {
	if (ast.children.length == 1)
		return this.stringify(ast.children[0]);
	var list = [];
	for (var i = 0; i < ast.children.length; i++)
		list.push(this.stringify(ast.children[i]));
	var separator = ',' + this.space;
	var before, after;
	if (this._stringifyEXPRUsesParan(ast)) {
		before = '(';
		after = ')';
	} else
		before = after = '';
	var str = before + list.join(separator) + after;
	return str;
}

this._stringifyEXPRUsesParan = function(ast) {
	if (!ast.parent)
		return false;
	if (this._stringifyEXPRUsesParan.neverUseParan[ast.parent.token.type])
		return false;
	return true;
}
this._stringifyEXPRUsesParan.neverUseParan = {};
this._stringifyEXPRUsesParan.neverUseParan[TOKEN.BLOCK] =
this._stringifyEXPRUsesParan.neverUseParan[TOKEN.DefaultStatement] =
this._stringifyEXPRUsesParan.neverUseParan[TOKEN.Do] =
this._stringifyEXPRUsesParan.neverUseParan[TOKEN.If] =
this._stringifyEXPRUsesParan.neverUseParan[TOKEN.For] =
this._stringifyEXPRUsesParan.neverUseParan[TOKEN.Return] =
this._stringifyEXPRUsesParan.neverUseParan[TOKEN.Throw] =
this._stringifyEXPRUsesParan.neverUseParan[TOKEN.While] =
this._stringifyEXPRUsesParan.neverUseParan[TOKEN.With] =
	true;

this.stringifyFILTER = function(ast) {
	var str = this.stringify(ast.children[0]);
	str += '.(';
	str += this.stringify(ast.children[1]);
	str += ')';
	return str;
}

this.stringifyFinally = function(ast) {
	var str = 'finally' + this.space + this.stringify(ast.children[0]);
	return str;
}

this.stringifyFor = function(ast) {
	var isEach = ast.children[0].token.type == TOKEN.Each;
	var str = 'for';
	if (isEach) str += ' each';
	str += this.space + '(';
	if (!isEach && ast.children.length > 2) {
		// normal for
		str += this.stringify(ast.children[0]) || this.space;
		str += ';' + this.space;
		if (ast.children[1].token.type != TOKEN.True)
			str += this.stringify(ast.children[1]);
		str += ';' + this.space + this.stringify(ast.children[2]);
	} else {
		// for-in or for-each-in
		str += this.stringify(ast.children[isEach ? 1 : 0]);
	}
	str += ')' + this._stringifyComposed(ast.children[ast.children.length-1]).str;
	return str;
}

this.stringifyFunction = function(ast) {
	var base = 0;
	var str = 'function';
	if (ast.children[base].token.type != TOKEN.ArgDecl) {
		str += ' ' + this.stringify(ast.children[base]);
		base++;
	}
	str += this.stringify(ast.children[base]);
	str += this.space;
	var strictOnlyInside = false;
	if (!this.strictMode && this.testStrict(ast.children[ast.children.length-1]))
		strictOnlyInside = true;
	str += this.stringify(ast.children[base+1]);
	if (strictOnlyInside)
		this.exitStrictMode();
	if (this._useParanToStringifyFunction(ast))
		str = '(' + str + ')';
	return str;
}

var alphaNum = /\w/;
this.stringifyIf = function(ast) {
	var str = 'if' + this.space + '(' + this.stringify(ast.children[0]) + ')';
	var inc = this._stringifyComposed(ast.children[1]);
	str += inc.str;
	if (ast.children.length == 3) {
		str += inc.indent ? this.indentLine : this.space;
		str += 'else';
		inc = this._stringifyComposed(ast.children[2]);
		if (alphaNum.test(inc.str.charAt(0)))
			str += ' ';
		str += inc.str;
	}
	return str;
}

this.stringifyITEM = function(ast) {
	var str = this.stringify(ast.children[0]);
	return str;
}

this.stringifyLABEL = function(ast) {
	return ast.children[0].token.text + ':' + this.indentLine + this.stringify(ast.children[1]);
}

this.stringifyOBJECT = function(ast) {
	var str = '{';
	this.incIndentation();
	if (ast.children)
		for (var i = 0; i < ast.children.length; i++) {
			if (i) str += ',';
			str += this.indentLine;
			if (ast.children[i].token.type === TOKEN.String) {
				str += Alkaline.JSInfo.stringToIdOrNumber(ast.children[i].token.text, this.kwNowStr);
				str += ':' + this.space + this.stringify(ast.children[i].children[0]);
			} else
				str += this.stringify(ast.children[i]);
		}

	this.decIndentation();
	if (ast.children && ast.children.length)
		str += this.indentLine;
	str += '}';
	// same paranthesizing rule for functions apply to object literals
	if (this._useParanToStringifyFunction(ast))
		str = '(' + str + ')';
	return str;
}

this.stringifyPostOp = function(ast) {
	var str = this.stringify(ast.children[0]);
	str += ast.children[1].token.text;
	return str;
}

this.stringifyReturn = function(ast) {
	var str = 'return';
	if (ast.children && ast.children.length)
		str += ' ' + this.stringify(ast.children[0]);
	return str;
}

this.stringifySELECT = function(ast) {
	var str = this.stringify(ast.children[0]);
	if (this._useParanForCALLorSELECT(ast))
		str = '(' + str + ')';
	var propertyName = ast.children[1].token.text;
	if (ast.children[1].token.type == TOKEN.String
		&& (this.idRx.test(propertyName) || (this.hasE4X && this.e4xGlobalSelect.test(propertyName)))
		&& !this.kwNow.contains(propertyName.substr(1, propertyName.length-2)))
		str += '.' + propertyName.substr(1, propertyName.length-2);
	else
		str += '[' + this.stringify(ast.children[1]) + ']';
	return str;
}

this.stringifySelectDescendants = function(ast) {
	var name = ast.children[1].token.text;
	name = name.substr(1, name.length-2);
	var str = this.stringify(ast.children[0]);
	str += '..';
	str += name;
	return str;
}

this.stringifySwitch = function(ast) {
	var str = 'switch' + this.space + '(' + this.stringify(ast.children[0]) + ')' + this.space + '{';
	this.incIndentation();
	for (var i = 1; i < ast.children.length; i++) {
		str += this.indentLine;
		str += this.stringify(ast.children[i]);
	}
	this.decIndentation();
	str += this.indentLine;
	str += '}';
	return str;
}

this.stringifyThrow = function(ast) {
	var str = 'throw';
	str += ' ' + this.stringify(ast.children[0]);
	return str;
}

this.stringifyTry = function(ast) {
	var str = 'try';
	for (var i = 0; i < ast.children.length; i++)
		str += this.space + this.stringify(ast.children[i]);
	return str;
}

this.stringifyUnaryOperator = function(ast) {
	var spacing = '';
	if (ast.children[0].token.type == TOKEN.UnaryOperator && (ast.token.text == '+' || ast.token.text == '-') && ast.children[0].token.text == ast.token.text)
		spacing = ' ';
	var str = this.stringify(ast.children[0]);
	if (ast.children[0].token.type == TOKEN.Operator)
		str = '(' + str + ')';
	str = ast.token.text + spacing + str;
	return str;
}

this.stringifyConst =
this.stringifyLet =
this.stringifyVar =
function(ast) {
	var list = [];
	for (var i = 0; i < ast.children.length; i++)
		list.push(this.stringify(ast.children[i]));
	var separator = ',' + this.space;
	return ast.token.text + ' ' + list.join(separator);
}

this.stringifyWhile = function(ast) {
	var str = 'while' + this.space + '(' + this.stringify(ast.children[0]) + ')';
	str += this._stringifyComposed(ast.children[1]).str;
	return str;
}

this.stringifyWith = function(ast) {
	var str = 'with' + this.space + '(' + this.stringify(ast.children[0]) + ')';
	str += this._stringifyComposed(ast.children[1]).str;
	return str;
}

this.stringifyNew = function(ast) {
	var str = this.stringify(ast.children[0]);
	if (ast.children[0].token.type == TOKEN.Operator)
		str = this.space + '(' + str + ')';
	else
		str = ' ' + str;
	str = ast.token.text + str;
	if (this.arguments.prettyPrint || ast.children.length > 1 || [TOKEN.SELECT, TOKEN.CALL].contains(ast.parent.token.type)) {
		str += '(';
		for (var i = 1; i < ast.children.length; i++) {
			if (i > 1)
				str += ',' + this.space;
			str += this.stringify(ast.children[i]);
		}
		str += ')';
	}
	return str;
}

this.stringifyDelete =
this.stringifyTypeOf =
this.stringifyVoid =
this.stringifyYield = function(ast) {
	var str = this.stringify(ast.children[0]);
	if (ast.children[0].token.type === TOKEN.Operator)
		str = this.space + '(' + str + ')';
	else if (ast.children[0].token.type === TOKEN.EXPR)
		str = this.space + str;
	else
		str = ' ' + str;
	str = ast.token.text + str;
	return str;
}

/*
this.stringifyQMark = function(ast) {
	var before, after;
	if (ast.parent && ast.parent.token.type == TOKEN.Operator) {
		before = '(';
		after = ')';
	} else
		before = after = '';
	return before + this.stringify(ast.children[0]) + this.space + '?' + this.space + this.stringify(ast.children[1]) + this.space + ':' + this.space + this.stringify(ast.children[2]) + after;
}
*/


this.stringifyIn =
this.stringifyInstanceOf =
this.stringifyOperator = function(ast) {
	var inter = (this.arguments.prettyPrint || ast.token.text === 'in' || ast.token.text === 'instanceof') ? ' ' : '';
	var before = '', after = '';
	if (ast.parent.token.type == TOKEN.Operator && this.stringifyOperatorUseParanInsideOp(ast)) {
		before = '(';
		after = ')';
	}
	var inter1 = inter;
	var inter2 = inter;
	if (!inter) {
		if (ast.children[0].token.type === TOKEN.PostOp)
			inter1 = ' ';
		if (ast.children[1].token.type === TOKEN.UnaryOperator && ast.children[1].token.type && ast.token.text === ast.children[1].token.text.charAt(0))
			inter2 = ' ';
	}
	var str = before + this.stringify(ast.children[0]) + inter1 + ast.token.text + inter2 + this.stringify(ast.children[1]);
	if (ast.token.text == '?')
		str += inter + ':' + inter + this.stringify(ast.children[2]);
	str += after;
	return str;
}

this.isRightToLeftAssociativeOp = function(op) {
	return op == "?" || this.opIsAssignment(op);
}
this.stringifyOperatorUseParanInsideOp = function(ast) {
	if (ast.token.text === ast.parent.token.text && Alkaline.JSInfo.associativeOperators.indexOf(ast.token.text) > -1)
		return false;
	//if (this.opIsAssignment(ast.token.text))
	//	return true;
	//if (this.opIsAssignment(ast.parent.token.text))
	//	return false;
	if (ast.parent.token.text === "?" && ast.childIndex > 0)
		return false;
	var precParent = this.getOpPriority(ast.parent.token.text);
	var precThis = this.getOpPriority(ast.token.text);
	if (precParent < precThis)
		return true;
	var rtl = this.isRightToLeftAssociativeOp(ast.token.text);
	if (precParent === precThis && ast.childIndex !== (!rtl ? 0 : ast.parent.children.length-1))
		return true;
	return false;
}


this.stringifyRegExp = function(ast) {
	return ast.token.text;
}

this.stringifyEMPTY = function(ast) {
	return '';
}

this.stringifyEach = function(ast) {
	return 'each';
}
this.stringifyFalse = function(ast) {
	return 'false';
}
this.stringifyGet = function(ast) {
	return 'get ' +
		//this.stringify(ast.children[0]) +	// accessor name
		Alkaline.JSInfo.stringToIdOrNumber(ast.children[0].token.text, this.kwNowStr) + // accessor name
		this.stringify(ast.children[1]) +	// ArgDecl
		this.space +
		this.stringify(ast.children[2]);	// BLOCK
}
this.stringifyNull = function(ast) {
	return 'null';
}
this.stringifySet = function(ast) {
	return 'set ' +
		//this.stringify(ast.children[0]) +	// accessor name
		Alkaline.JSInfo.stringToIdOrNumber(ast.children[0].token.text, this.kwNowStr) + // accessor name
		this.stringify(ast.children[1]) +	// ArgDecl
		this.space +
		this.stringify(ast.children[2]);	// BLOCK
}
this.stringifyTrue = function(ast) {
	return 'true';
}


this.stringifyNumber =
this.stringifyString = function(ast) {
	return ast.token.text;
}

this.stringifyId = function(ast) {
	var str = ast.token.text;
	if (ast.children && ast.children.length && ast.parent.token.type !== TOKEN.OBJECT)
		str += this.space + '=' + this.space + this.stringify(ast.children[0]);
	return str;
}

//
//		E4X
//

this.stringifyXmlNodeList = function(ast) {
	var str = '<>';
	for (var i = 0; i < ast.children.length; i++)
		str += this.stringify(ast.children[i]);
	str += '</>';
	return str;
}

this.stringifyXmlElement = function(ast) {
	var str = '<';
	var longForm = ast.children[0].token.type === TOKEN.TokenList;
	if (longForm)
		str += this.stringify(ast.children[0].children[0]);
	else
		str += this.stringify(ast.children[0]);
	var nextItem = 1;
	while (nextItem < ast.children.length && ast.children[nextItem].token.type === TOKEN.XmlAttribute)
		str += ' ' + this.stringify(ast.children[nextItem++]);
	if (longForm) {
		str += '>';
		while (nextItem < ast.children.length)
			str += this.stringify(ast.children[nextItem++]);
		str += '</';
		str += this.stringify(ast.children[0].children[1]);
		str += '>';
	} else
		str += this.space + '/>';
	return str;
}

this.stringifyXmlAttribute = function(ast) {
	var str = this.stringify(ast.children[0]);
	str += '=';
	str += this.stringify(ast.children[1]);
	return str;
}

this.stringifyXmlCData = 
this.stringifyXmlTextNode =
	function(ast) {
		return ast.token.text;
	}

//
//		/E4X
//


this._stringifyNotImplemented = function(ast) {
	this.compileResult.stdErr('Stringify of ' + Alkaline.JSInfo.tokenName[ast.token.type] + ' is not implemented (%o).', ast);
}

for (var i = 0; i < tokenNames.length; i++)
	this['stringify' + TOKEN[tokenNames[i]]] = this['stringify' + tokenNames[i]] || (this['stringify' + tokenNames[i]] = this._stringifyNotImplemented);


});